﻿/*
 *  Author: Louis A. Leskoviansky
 *  Company: Artemis Solutions Group (http://www.artemis-solutions.com)
 */


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Web;

namespace AjaxControlFramework.Reflection
{
    public delegate object ObjectCreate();


    public static class TypeCache
    {
        //------// Properties \\--------------------------------------------\\
        private static object InternalConstructorCollectionLock = new object();


        private static ReflectiveCacheCollection<ConstructorCacheEntry> InternalConstructorCollection
        {
            get
            {
                ReflectiveCacheCollection<ConstructorCacheEntry> collection = (ReflectiveCacheCollection<ConstructorCacheEntry>)HttpContext.Current.Cache["AjaxControlFramework.Reflection.TypeCache.InternalConstructorCollection"];

                if (collection == null)
                {
                    collection = new ReflectiveCacheCollection<ConstructorCacheEntry>();
                    HttpContext.Current.Cache["AjaxControlFramework.Reflection.TypeCache.InternalConstructorCollection"] = collection;
                }

                return collection;
            }
        }
        //------\\ Properties //--------------------------------------------//



        //------// Methods \\-----------------------------------------------\\
        public static bool CreateObject<T>(out T newObject) where T : new()
        {
            return CreateObject<T>(out newObject, false);
        }


        public static bool CreateObject<T>(out T newObject, bool throwException) where T : new()
        {
            newObject = default(T);
            object tempObject = newObject as object;

            bool result = CreateObject(typeof(T), out tempObject, throwException);

            newObject = (T)tempObject;
            return result;
        }


        public static bool CreateObject(Type objectType, out object newObject)
        {
            return CreateObject(objectType, out newObject, false);
        }


        public static bool CreateObject(Type instanceType, out object newObject, bool throwException)
        {
            if (instanceType == null)
            {
                if (throwException) { throw new ArgumentNullException("instanceType"); }
                else
                {
                    newObject = null;
                    return false;
                }
            }


            ConstructorCacheEntry cacheEntry = null;

            lock (InternalConstructorCollectionLock)
            {
                cacheEntry = InternalConstructorCollection[instanceType, instanceType.Name];
            }

            if (cacheEntry != null)
            {
                if (cacheEntry.Delegate == null)
                {
                    newObject = null;
                    return false;
                }
                else
                {
                    newObject = cacheEntry.Delegate();
                    return true;
                }
            }
            else
            {
                ObjectCreate constructor = CacheConstructor(instanceType);

                if (constructor == null)
                {
                    newObject = null;
                    return false;
                }
                else
                {
                    newObject = constructor();
                    return true;
                }
            }
        }


        public static ObjectCreate CacheConstructor(Type instanceType)
        {
            if (instanceType == null) { throw new ArgumentNullException("instanceType"); }

            ConstructorInfo constructorInfo = instanceType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
            if (constructorInfo == null) { throw new InvalidOperationException(String.Format("A default constructor for the {0} Type that was provided could not be found.", instanceType.FullName)); }

            return CacheConstructor(instanceType, constructorInfo);
        }


        public static ObjectCreate CacheConstructor(Type instanceType, ConstructorInfo constructorInfo)
        {
            if (instanceType == null) { throw new ArgumentNullException("instanceType"); }
            if (constructorInfo == null) { throw new ArgumentNullException("constructorInfo"); }


            // Locking isn't going any further than this. Won't be checking again after the lock to see if the entry is STILL not in the 
            // internal collection as I want to allow for overwritting of cache entries.
            lock (InternalConstructorCollectionLock)
            {
                ConstructorCacheEntry cacheEntry = new ConstructorCacheEntry(instanceType.Name);

                DynamicMethod dynamicMethod = new DynamicMethod(instanceType.Name, instanceType, Type.EmptyTypes, true);

                ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                ilGenerator.Emit(OpCodes.Newobj, constructorInfo);
                ilGenerator.Emit(OpCodes.Ret);

                ObjectCreate constructor = (ObjectCreate)dynamicMethod.CreateDelegate(typeof(ObjectCreate));

                if (constructor == null)
                {
                    InternalConstructorCollection[instanceType, instanceType.Name] = cacheEntry;
                    return null;
                }

                cacheEntry.Info = constructorInfo;
                cacheEntry.Delegate = constructor;

                InternalConstructorCollection[instanceType, instanceType.Name] = cacheEntry;
                return constructor;
            }
        }
        //------\\ Methods //-----------------------------------------------//
    }
}
